package org.codebridge.translator;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

public class Translator {

	private final String apiKey;
	
	/**
	 * Creates a new Translator instance using the passed API key.
	 * An API key can be obtained at http://www.bing.com/developers/createapp.aspx
	 * @param apiKey Bing Developer API Key
	 */
	public Translator(String apiKey) {
		this.apiKey = apiKey;
	}
	
	/**
	 * Generates an XML string that can be used in a TranslateArray request
	 * @param texts Array of Strings to be translated
	 * @param languageFrom Language of original text
	 * @param languageTo Desired language of translation
	 * @return XML String to be used in request
	 * @throws Exception
	 */
	private String generateTranslateArrayXmlRequest(String[] texts, Language languageFrom,
			Language languageTo) throws Exception {
		
		Document doc = DocumentBuilderFactory.newInstance()
				.newDocumentBuilder().newDocument();

		Element root = doc.createElement("TranslateArrayRequest");
		doc.appendChild(root);

		//AppId
		Element eAppId = doc.createElement("AppId");
		eAppId.appendChild(doc.createTextNode(this.apiKey));
		root.appendChild(eAppId);
		
		//From
		Element eFrom = doc.createElement("From");
		eFrom.appendChild(doc.createTextNode(languageFrom.getRepresenation()));
		root.appendChild(eFrom);
		
		//Options
		Element eOpts = doc.createElement("Options");
		Element eChildOpt = doc.createElement("Category");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eOpts.appendChild(eChildOpt);
		
		eChildOpt = doc.createElement("ContentType");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eChildOpt.appendChild(doc.createTextNode("text/plain"));
		eOpts.appendChild(eChildOpt);
		
		eChildOpt = doc.createElement("ReservedFlags");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eOpts.appendChild(eChildOpt);
		
		eChildOpt = doc.createElement("State");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eOpts.appendChild(eChildOpt);
		
		eChildOpt = doc.createElement("Uri");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eOpts.appendChild(eChildOpt);
		
		eChildOpt = doc.createElement("User");
		eChildOpt.setAttribute("xmlns", "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2");
		eOpts.appendChild(eChildOpt);
		
		root.appendChild(eOpts);
		
		//Text
		Element eTexts = doc.createElement("Texts");
		Element eTextChild;
		for(String s : texts) {
			eTextChild = doc.createElement("string");
			eTextChild.setAttribute("xmlns", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
			eTextChild.appendChild(doc.createTextNode(s));
			eTexts.appendChild(eTextChild);
		}
		root.appendChild(eTexts);
	
		//To
		Element eTo = doc.createElement("To");
		eTo.appendChild(doc.createTextNode(languageTo.getRepresenation()));
		root.appendChild(eTo);

		TransformerFactory transfac = TransformerFactory.newInstance();
		Transformer trans = transfac.newTransformer();
		trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		trans.setOutputProperty(OutputKeys.INDENT, "yes");

		// create string from xml tree
		StringWriter sw = new StringWriter();
		StreamResult result = new StreamResult(sw);
		DOMSource source = new DOMSource(doc);
		trans.transform(source, result);
		String xmlString = sw.toString();
		return xmlString;
	}
	
	/**
	 * Makes TranslateArray request to Microsoft server
	 * @param xmlString XML request to be used (use generateTranslateArrayXmlRequest to create)
	 * @return XML response from Microsoft
	 * @throws Exception
	 */
	private static String getTranslateArrayResponse(String xmlString) throws Exception {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        String uri = "http://api.microsofttranslator.com/v2/Http.svc/TranslateArray";
        HttpPost post = new HttpPost(uri);
        
        StringEntity reqEntity = new StringEntity(xmlString, "utf-8");
        reqEntity.setContentType("application/xml");
        
        post.setEntity(reqEntity);        
        HttpResponse response = httpclient.execute(post);
        HttpEntity resEntity = response.getEntity();
        return EntityUtils.toString(resEntity);
	}
	
	/**
	 * Processes an XML response from TranslateArray request into an array of translated Strings
	 * @param xml XML to be processed
	 * @return Array of translated Strings
	 * @throws Exception 
	 */
	private static String[] processTranslateArrayResponse(String xml) throws Exception {
		ArrayList<String> ret = new ArrayList<String>();
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		
		InputSource is = new InputSource( new StringReader(xml) );
		Document doc = dBuilder.parse(is);
		
		doc.getDocumentElement().normalize();
		
		NodeList nList = doc.getElementsByTagName("TranslatedText");
		for(int i = 0; i < nList.getLength(); i++) {
			ret.add(nList.item(i).getTextContent());
		}
		return ret.toArray(new String[0]);
	}
	
	/**
	 * Translates an array of Strings from one language to another
	 * @param texts Array of Strings to be translated (each 10,240 characters or less)
	 * @param languageFrom Language of Strings to be translated
	 * @param languageTo Desired language to be translated to 
	 * @return Array of translated Strings
	 * @throws Exception
	 */
	public String[] translateArray(String[] texts, Language languageFrom, Language languageTo) throws Exception {
		for(String s : texts) {
			if(s.length() > 10240) {
				throw new RuntimeException("Each string to be translated must be 10,240 characters or less.");
			}
		}
		
		String xmlString = generateTranslateArrayXmlRequest(texts, languageFrom, languageTo);
		String response =  getTranslateArrayResponse(xmlString);
		return processTranslateArrayResponse(response);
		
	}	
}
